// Copyright 2006-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
//
// Declares the interface to in-memory metrics capture

#ifndef OMAHA_STATSREPORT_METRICS_H__
#define OMAHA_STATSREPORT_METRICS_H__

#include <iterator>

#include "base/basictypes.h"
#include "omaha/base/highres_timer-win32.h"
#include "omaha/base/logging/logging.h"

/// Macros to declare & define named & typed metrics.
/// Put declarations in headers or in cpp files, where you need access
/// to the metrics. For each declared metric, there must be precisely
/// one definition in a compilation unit someplace.

/// A count metric should be used to report anything that monotonically
/// increases.
/// Examples:
///    # event count
///      how often does this condition hit, this function get called
///    # aggregate sums
///      how many bytes are written
#define DECLARE_METRIC_count(name)   DECLARE_METRIC(CountMetric, name)
#define DEFINE_METRIC_count(name)   DEFINE_METRIC(CountMetric, name)

/// Use timing metrics to report on the performance of important things.
/// A timing metric will report the count of occurrences, as well as the
/// average, min and max times.
/// Samples are measured in milliseconds if you use the TIME_SCOPE macro
/// or the HighResTimer class to collect samples.
#define DECLARE_METRIC_timing(name)  DECLARE_METRIC(TimingMetric, name)
#define DEFINE_METRIC_timing(name)  DEFINE_METRIC(TimingMetric, name)

/// Collects a sample from here to the end of the current scope, and
/// adds the sample to the timing metric supplied
#define TIME_SCOPE(timing) \
  stats_report::TimingSample __xxsample__(timing)

/// Use integer metrics to report runtime values that fluctuate.
/// Examples:
///    # object count
///      How many objects of some type exist
///    # disk space or memory
///      How much disk space or memory is in use
#define DECLARE_METRIC_integer(name) DECLARE_METRIC(IntegerMetric, name)
#define DEFINE_METRIC_integer(name) DEFINE_METRIC(IntegerMetric, name)


/// Use boolean metrics to report the occurrence of important but rare events
/// or conditions. Note that a boolean metric is tri-state, so you typically
/// want to set it only in one direction, and typically to true.
/// Setting a boolean metric one way or another on a trigger event will report
/// the setting of the boolean immediately prior to reporting, which is
/// typically not what you want.
#define DECLARE_METRIC_bool(name)    DECLARE_METRIC(BoolMetric, name)
#define DEFINE_METRIC_bool(name)    DEFINE_METRIC(BoolMetric, name)


/// Implementation macros
#define DECLARE_METRIC(type, name) \
  namespace omaha_client_statsreport { \
  extern stats_report::type metric_##name; \
  } \
  using omaha_client_statsreport::metric_##name

#define DEFINE_METRIC(type, name) \
  namespace omaha_client_statsreport { \
  stats_report::type metric_##name(#name, \
  &stats_report::g_global_metric_storage); \
  } \
  using omaha_client_statsreport::metric_##name


namespace stats_report {

enum MetricType {
  // use zero for invalid, because global storage defaults to zero
  kInvalidType = 0,
  kCountType,
  kTimingType,
  kIntegerType,
  kBoolType
};

// fwd.
struct MetricCollectionBase;
class MetricCollection;
class MetricBase;
class IntegerMetricBase;
class CountMetric;
class TimingMetric;
class IntegerMetric;
class BoolMetric;

/// Base class for all stats instances.
/// Stats instances are chained together against a MetricCollection to
/// allow enumerating stats.
///
/// MetricCollection is factored into a class to make it easier to unittest
/// the implementation.
class MetricBase {
public:
  /// @name Downcasts
  /// @{
  CountMetric &AsCount();
  TimingMetric &AsTiming();
  IntegerMetric &AsInteger();
  BoolMetric &AsBool();

  const CountMetric &AsCount() const;
  const TimingMetric &AsTiming() const;
  const IntegerMetric &AsInteger() const;
  const BoolMetric &AsBool() const;
  /// @}

  /// @name Accessors
  /// @{
  MetricType type() const { return type_; }
  MetricBase *next() const { return next_; }
  const char *name() const { return name_; }
  /// @}

  // TODO(omaha): does this need to be virtual?
  virtual ~MetricBase() = 0;

protected:
  class ObjectLock;
  void Lock() const;
  void Unlock() const;

  /// Constructs a MetricBase and adds to the provided MetricCollection.
  /// @note Metrics can only be constructed up to the point where the
  ///     MetricCollection is initialized, and there's no locking performed.
  ///     The assumption is that outside unit tests, Metrics will we declared
  ///     as static/global variables, and initialized at static initialization
  ///     time - and static initialization is single-threaded.
  MetricBase(const char *name, MetricType type, MetricCollectionBase *coll);

  /// Constructs a named typed MetricBase
  MetricBase(const char *name, MetricType type);

  /// Our name
  char const *const name_;

  /// type of this metric
  MetricType const type_;

  /// chains to next stat instance
  MetricBase *const next_;

  /// The collection we're created against
  MetricCollectionBase *const coll_;

private:
  DISALLOW_COPY_AND_ASSIGN(MetricBase);
};

/// Must be a POD
struct MetricCollectionBase {
  bool initialized_;
  MetricBase *first_;
};

/// Inherit from base, which is a POD and can be initialized at link time.
///
/// The global MetricCollection is aliased to a link-time initialized
/// instance of MetricCollectionBase, and must not extend the size of its
/// base class.
class MetricCollection: public MetricCollectionBase {
public:
  MetricCollection() {
    initialized_ = false;
    first_ = NULL;
  }
  ~MetricCollection() {
    DCHECK(NULL == first_);
  }

  /// Initialize must be called after all metrics have been added to the
  /// collection, but before enumerating it for e.g. aggregation or reporting.
  /// The intent is that outside unit tests, there will only be the global
  /// metrics collection, which will accrue all metrics defined with the
  /// DEFINE_METRIC_* macros.
  /// Typically you'd call Initialize very early in your main function, and
  /// Uninitialize towards the end of main.
  /// It is an error to Initialize() when the collection is initialized().
  void Initialize();

  /// Uninitialize must be called before removing (deleting or deconstructing)
  /// metrics from the collection.
  /// It is an error to Uninitialize() when the collection is !initialized().
  void Uninitialize();

  MetricBase *first() const { return first_; }
  bool initialized() const { return initialized_; }

private:
  using MetricCollectionBase::initialized_;
  using MetricCollectionBase::first_;

  DISALLOW_COPY_AND_ASSIGN(MetricCollection);

  /// MetricBase is intimate with us
  friend class MetricBase;
};

/// Implements a forward_iterator for MetricCollection.
class MetricIterator: public std::iterator<std::forward_iterator_tag,
                                           MetricBase *> {
public:
  MetricIterator() : curr_(NULL) {
  }
  MetricIterator(const MetricIterator &other) : curr_(other.curr_) {
  }
  MetricIterator(const MetricCollection &coll) : curr_(coll.first()) {
    DCHECK(coll.initialized());
  }

  MetricBase *operator*() {
    return curr_;
  }
  const MetricBase *operator*() const {
    return curr_;
  }
  MetricBase *operator->() {
    return curr_;
  }
  const MetricBase *operator->() const {
    return curr_;
  }
  MetricIterator operator++() { // preincrement
    if (curr_)
      curr_ = curr_->next();

    return (*this);
  }
  MetricIterator operator++(int) {// postincrement
    MetricIterator ret = *this;
    ++*this;
    return (ret);
  }

private:
  MetricBase *curr_;
};

inline bool operator == (const MetricIterator &a, const MetricIterator &b) {
  return *a == *b;
}
inline bool operator != (const MetricIterator &a, const MetricIterator &b) {
  return !operator == (a, b);
}

/// Globally defined counters are registered here
extern MetricCollectionBase g_global_metric_storage;

/// And more conveniently accessed through here
extern MetricCollection &g_global_metrics;

/// Base class for integer metrics
class IntegerMetricBase: public MetricBase {
public:
  /// Sets the current value
  void Set(int64 value);

  /// Retrieves the current value
  int64 value() const;

  void operator ++ ()     { Increment(); }
  void operator ++ (int)  { Increment(); }
  void operator += (int64 addend) { Add(addend); }

protected:
  IntegerMetricBase(const char *name,
                    MetricType type,
                    MetricCollectionBase *coll)
      : MetricBase(name, type, coll), value_(0) {
  }
  IntegerMetricBase(const char *name, MetricType type, int64 value)
      : MetricBase(name, type), value_(value) {
  }

  void Increment();
  void Decrement();
  void Add(int64 value);
  void Subtract(int64 value);

  int64 value_;

private:
  DISALLOW_COPY_AND_ASSIGN(IntegerMetricBase);
};

/// A count metric is a cumulative counter of events.
class CountMetric: public IntegerMetricBase {
public:
  CountMetric(const char *name, MetricCollectionBase *coll)
      : IntegerMetricBase(name, kCountType, coll) {
  }

  CountMetric(const char *name, int64 value)
      : IntegerMetricBase(name, kCountType, value) {
  }

  /// Nulls the metric and returns the current values.
  int64 Reset();

private:
  DISALLOW_COPY_AND_ASSIGN(CountMetric);
};

class TimingMetric: public MetricBase {
public:
  struct TimingData {
    uint32 count;
    uint32 align; // allow access to the alignment gap between count and sum,
                  // makes it esier to unittest.
    int64 sum; // ms
    int64 minimum; // ms
    int64 maximum; // ms
  };

  TimingMetric(const char *name, MetricCollectionBase *coll)
      : MetricBase(name, kTimingType, coll) {
    Clear();
  }

  TimingMetric(const char *name, const TimingData &value)
      : MetricBase(name, kTimingType), data_(value) {
  }

  uint32 count() const;
  int64 sum() const;
  int64 minimum() const;
  int64 maximum() const;
  int64 average() const;

  /// Adds a single sample to the metric
  /// @param time_ms time (in milliseconds) for this sample
  void AddSample(int64 time_ms);

  /// Adds count samples to the metric
  /// @note use this when capturing time over a variable number of items to
  ///     normalize e.g. download time per byte or KB. This records one sample
  ///     over count items, which is numerically more stable for the average
  ///     than dividing the captured time by the item count. As a side benefit
  ///     the timer will also record the item count.
  /// @note if count == 0, no sample will be recorded
  /// @param count number of samples to add
  /// @param total_time_ms the total time consumed by all the "count" samples
  void AddSamples(int64 count, int64 total_time_ms);

  /// Nulls the metric and returns the current values.
  TimingData Reset();

private:
  DISALLOW_COPY_AND_ASSIGN(TimingMetric);

  void Clear();

  TimingData data_;
};

/// A convenience class to sample the time from construction to destruction
/// against a given timing metric.
class TimingSample {
public:
  /// @param timing the metric the sample is to be tallied against
  explicit TimingSample(TimingMetric &timing) : timing_(timing), count_(1) {
  }

  /// @param timing the metric the sample is to be tallied against
  /// @param item_count count of items processed, used to divide the sampled
  ///     time so as to capture time per item, which is often a better measure
  ///     than the total time over a varying number of items.
  TimingSample(TimingMetric &timing, uint32 item_count) : timing_(timing),
      count_(item_count) {
  }

  ~TimingSample() {
    // We discard samples with a zero count and samples where the elapsed time
    // is out of range.
    const int64 time_ms(static_cast<int64>(timer_.GetElapsedMs()));
    if (time_ms < 0) {
      return;
    }
    if (count_ == 1)
      timing_.AddSample(time_ms);
    else
      timing_.AddSamples(count_, time_ms);
  }

  /// @name Accessors
  /// @{
  uint32 count() const { return count_; }
  void set_count(uint32 count) { count_ = count; }
  /// @}

private:
  /// Collects the sample for us.
  omaha::HighresTimer timer_;

  /// The metric we tally against.
  TimingMetric &timing_;

  /// The item count we divide the captured time by
  uint32 count_;

  DISALLOW_COPY_AND_ASSIGN(TimingSample);
};

/// An integer metric is used to sample values that vary over time.
/// On aggregation the instantaneous value of the integer metric is captured.
class IntegerMetric: public IntegerMetricBase {
public:
  IntegerMetric(const char *name, MetricCollectionBase *coll)
      : IntegerMetricBase(name, kIntegerType, coll) {
  }

  IntegerMetric(const char *name, int64 value)
      : IntegerMetricBase(name, kIntegerType, value) {
  }

  void operator = (int64 value)   { Set(value); }

  void operator -- ()     { Decrement(); }
  void operator -- (int)  { Decrement(); }
  void operator -= (int64 sub)    { Subtract(sub); }

private:
  DISALLOW_COPY_AND_ASSIGN(IntegerMetric);
};

/// A bool metric is tri-state, and can be:
///    - unset,
///    - true or
///    - false
/// to match other metrics, which are implicitly unset if they've not changed
/// from their initial value.
class BoolMetric: public MetricBase {
public:
  /// Values we can take
  enum TristateBoolValue {
    kBoolUnset = -1,
    kBoolFalse,
    kBoolTrue,
  };

  BoolMetric(const char *name, MetricCollectionBase *coll)
        : MetricBase(name, kBoolType, coll), value_(kBoolUnset) {
  }

  BoolMetric(const char *name, uint32 value)
        : MetricBase(name, kBoolType) {
    switch (value) {
     case kBoolFalse:
     case kBoolTrue:
      value_ = static_cast<TristateBoolValue>(value);
      break;

     default:
      DCHECK(false && "Unexpected tristate bool value on construction");
      value_ = kBoolUnset;
    }
  }

  /// Sets the flag to the provided value.
  void Set(bool value);

  void operator = (bool value) {
    Set(value);
  }

  /// Nulls the metric and returns the current values.
  TristateBoolValue Reset();

  /// Returns the current value - not threadsafe
  TristateBoolValue value() const { return value_; };

private:
  DISALLOW_COPY_AND_ASSIGN(BoolMetric);

  TristateBoolValue value_;
};

inline CountMetric &MetricBase::AsCount() {
  DCHECK_EQ(kCountType, type());

  return static_cast<CountMetric&>(*this);
}

inline TimingMetric &MetricBase::AsTiming() {
  DCHECK_EQ(kTimingType, type());

  return static_cast<TimingMetric&>(*this);
}

inline IntegerMetric &MetricBase::AsInteger() {
  DCHECK_EQ(kIntegerType, type());

  return static_cast<IntegerMetric&>(*this);
}

inline BoolMetric &MetricBase::AsBool() {
  DCHECK_EQ(kBoolType, type());

  return static_cast<BoolMetric&>(*this);
}

inline const CountMetric &MetricBase::AsCount() const {
  DCHECK_EQ(kCountType, type());

  return static_cast<const CountMetric&>(*this);
}

inline const TimingMetric &MetricBase::AsTiming() const {
  DCHECK_EQ(kTimingType, type());

  return static_cast<const TimingMetric&>(*this);
}

inline const IntegerMetric &MetricBase::AsInteger() const {
  DCHECK_EQ(kIntegerType, type());

  return static_cast<const IntegerMetric&>(*this);
}

inline const BoolMetric &MetricBase::AsBool() const {
  DCHECK_EQ(kBoolType, type());

  return static_cast<const BoolMetric&>(*this);
}

}  // namespace stats_report

#endif  // OMAHA_STATSREPORT_METRICS_H__
